package com.ubi.core.log;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.ubi.core.framework.GlobalConfig;
import com.ubi.core.utils.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.MDC;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Date;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

public class LoggingEventProcessor {

    private static final int MAX_HOLD_SIZE = 5000;

    private final PatternLayout layout;

    private final String logFolder;

    private final Queue<ILoggingEvent> events = new ConcurrentLinkedQueue<>();

    private final AtomicInteger eventSize = new AtomicInteger(0);

    private final ReentrantLock reentrantLock = new ReentrantLock();

    private volatile boolean hold = true;

    private volatile Writer writer;

    public LoggingEventProcessor(PatternLayout patternLayout, String logFolder) {
        this.layout = patternLayout;
        this.logFolder = logFolder;
    }

    public void process(ILoggingEvent event) throws IOException {
        if (hold) {
            addEvent(event);
            if (shouldFlushLog(event)) {
                flushLog();
                hold = false;
            }
        } else {
            write(event);
        }
    }

    public void clearUp(boolean flushTraceLog) throws IOException {
        if (flushTraceLog) {
            flushLog();
        }

        if (logFolder == null) {
            IOUtils.flush(writer);
        } else {
            IOUtils.close(writer);
        }
    }

    void addEvent(ILoggingEvent event) {
        events.add(event);
        eventSize.getAndIncrement();
    }

    boolean shouldFlushLog(ILoggingEvent event) {
        return event.getLevel().isGreaterOrEqual(Level.WARN) || events.size() > MAX_HOLD_SIZE;
    }

    void flushLog() throws IOException {
        try {
            reentrantLock.lock();
            while (true) {
                if (writer == null) {
                    writer = createWriter();
                }
                ILoggingEvent event = events.poll();
                if (event == null) {
                    return;
                }
                write(event);
            }
        } finally {
            reentrantLock.unlock();
        }
    }

    void write(ILoggingEvent event) throws IOException {
        String log = layout.doLayout(event);
        writer.write(log);
    }

    private Writer createWriter() throws FileNotFoundException {
        if (logFolder == null) {
            return new BufferedWriter(new OutputStreamWriter(System.err, GlobalConfig.CHARSET_UTF8));
        }
        String logFilePath = generateLogFilePath(getAction(), new Date(), getRequestId());
        File logFile = new File(logFilePath);
        createParentFolder(logFile);
        return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile, true), GlobalConfig.CHARSET_UTF8));
    }

    private void createParentFolder(File logFile) {
        File folder = logFile.getParentFile();
        folder.mkdirs();
    }

    /**
     * 生成最终的
     */
    String generateLogFilePath(String action, Date targetDate, String requestId) {
        String sequence = RandomStringUtils.randomAlphanumeric(5);
        return String.format("%1$s/%2$tY/%2$tm/%2$td/%3$s/%2$tH%2$tM.%4$s.%5$s.log", logFolder, targetDate, action, requestId, sequence);
    }

    String getAction() {
        String action = MDC.get(LogConstants.MDC_ACTION);
        if (action == null) {
            action = "unknown";
        }
        return action;
    }

    String getRequestId() {
        // String requestId = SaasContextHolder.getLogRequestId();
        // if (StringUtils.isNotEmpty(requestId)) {
        // return requestId;
        // }
        return UUID.randomUUID().toString();
    }
}
